package com.luoyx.vjsb.activiti.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.luoyx.vjsb.activiti.entity.HiAdditional;
import com.luoyx.vjsb.activiti.entity.TaskAssignee;
import com.luoyx.vjsb.activiti.service.IActivityService;
import com.luoyx.vjsb.activiti.service.ITaskAssigneeService;
import com.luoyx.vjsb.activiti.vo.CurrentTaskVO;
import com.luoyx.vjsb.activiti.vo.ProcessInstanceVO;
import com.luoyx.vjsb.common.query.CommonQuery;
import com.luoyx.vjsb.common.util.AuthUtil;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.rest.service.api.RestResponseFactory;
import org.activiti.rest.service.api.repository.ModelRequest;
import org.activiti.rest.service.api.repository.ModelResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_DESCRIPTION;
import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_NAME;

/**
 * <p>
 *
 * </p>
 *
 * @author luoyuanxiang <p>luoyuanxiang.github.io</p>
 * @since 2020/5/15 17:12
 */
@Slf4j
@Service
public class ActivityServiceImpl implements IActivityService {

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    protected RestResponseFactory restResponseFactory;

    @Resource
    private ITaskAssigneeService iTaskAssigneeService;

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private TaskService taskService;

    @Resource
    private AuthUtil authUtil;

    /**
     * 获取分页信息
     *
     * @param query 查询条件
     * @return 模型
     */
    @Override
    public Page<Model> getPage(CommonQuery query) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        modelQuery.orderByLastUpdateTime().desc();
        if (StrUtil.isNotBlank(query.getSearchText())) {
            modelQuery.modelNameLike("%" + query.getSearchText() + "%");
        }
        Page<Model> page = new Page<>(query.getCurrent(), query.getSize());
        List<Model> resultList = modelQuery.listPage((int) ((query.getCurrent() - 1) * query.getSize()), (int) query.getSize());
        page.setTotal(modelQuery.count()).setRecords(resultList);
        return page;
    }

    /**
     * 创建模型
     *
     * @param name        模型名称
     * @param key         模型key
     * @param description 模型描述
     * @return 模型id
     */
    @Override
    public String createModeler(String name, String key, String description) {
        try {
            ObjectNode editorNode = objectMapper.createObjectNode();
            editorNode.put("id", "canvas");
            editorNode.put("resourceId", "canvas");
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
            editorNode.set("stencilset", stencilSetNode);

            ObjectNode modelObjectNode = objectMapper.createObjectNode();
            modelObjectNode.put(MODEL_NAME, name);
            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
            description = StringUtils.defaultString(description);
            modelObjectNode.put(MODEL_DESCRIPTION, description);

            Model newModel = repositoryService.newModel();
            newModel.setMetaInfo(modelObjectNode.toString());
            newModel.setName(name);
            newModel.setKey(StringUtils.defaultString(key));

            repositoryService.saveModel(newModel);
            repositoryService.addModelEditorSource(newModel.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));

            return newModel.getId();
        } catch (Exception e) {
            log.error("创建模型失败：", e);
        }
        return null;
    }

    /**
     * 修改模型
     *
     * @param id           模型id
     * @param modelRequest 模型实体类
     * @return 模型实体类
     */
    @Override
    public ModelResponse updateModel(String id, ModelRequest modelRequest) {
        Model model = repositoryService.createModelQuery().modelId(id).singleResult();

        if (modelRequest.isCategoryChanged()) {
            model.setCategory(modelRequest.getCategory());
        }
        if (modelRequest.isDeploymentChanged()) {
            model.setDeploymentId(modelRequest.getDeploymentId());
        }
        if (modelRequest.isKeyChanged()) {
            model.setKey(modelRequest.getKey());
        }
        if (modelRequest.isMetaInfoChanged()) {
            model.setMetaInfo(modelRequest.getMetaInfo());
        }
        if (modelRequest.isNameChanged()) {
            model.setName(modelRequest.getName());
        }
        if (modelRequest.isVersionChanged()) {
            model.setVersion(modelRequest.getVersion());
        }
        if (modelRequest.isTenantIdChanged()) {
            model.setTenantId(modelRequest.getTenantId());
        }

        repositoryService.saveModel(model);
        return restResponseFactory.createModelResponse(model);
    }

    /**
     * 部署模型
     *
     * @param modelId 模型id
     * @return b
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deploy(String modelId) {
        try {
            Model modelData = repositoryService.createModelQuery().modelId(modelId).singleResult();
            ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            byte[] bpmnBytes;

            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            bpmnBytes = new BpmnXMLConverter().convertToXML(model);

            String processName = modelData.getName() + ".bpmn20.xml";
            Deployment deployment = repositoryService.createDeployment()
                .name(modelData.getName())
                .key(modelData.getKey())
                .addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8))
                .category(modelData.getName())
                .deploy();
            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData);
            log.info("部署成功，部署ID=" + deployment.getId());
            Collection<FlowElement> flowElements = activityListFlowElement(modelData.getId());
            flowElements.parallelStream().forEach(flowElement -> {
                // 获取模型对应节点的完成角色
                TaskAssignee taskAssignee = iTaskAssigneeService.queryByModelIdAndActivityId(modelData.getId(), flowElement.getId());
                if (taskAssignee != null) {
                    taskAssignee.setDeploymentId(deployment.getId())
                        .setUpdateTime(DateUtil.date());
                    iTaskAssigneeService.updateById(taskAssignee);
                } else {
                    taskAssignee = new TaskAssignee();
                    taskAssignee.setDeploymentId(deployment.getId())
                        .setActivityId(flowElement.getId())
                        .setModelId(modelData.getId())
                        .setCreateTime(DateUtil.date())
                        .setUpdateTime(DateUtil.date());
                    iTaskAssigneeService.save(taskAssignee);
                }
            });
            return true;
        } catch (Exception e) {
            log.error("根据模型部署流程失败：modelId={}", modelId, e);

        }
        return false;
    }

    /**
     * 获取流程节点
     *
     * @param modelId 模型id
     * @return 流程节点
     */
    @Override
    public Collection<FlowElement> activityListFlowElement(String modelId) throws IOException {
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelId));
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        flowElements = flowElements.parallelStream().filter(e -> e instanceof UserTask).collect(Collectors.toList());
        return flowElements;
    }

    /**
     * 启动流程
     *
     * @param deployId 流程部署id
     * @param map      流程变量
     * @return ProcessInstanceVO
     */
    @Override
    public ProcessInstanceVO startProcess(String deployId, Map<String, Object> map) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
            .deploymentId(deployId)
            .singleResult();
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), map);
        ProcessInstanceVO processInstanceVO = new ProcessInstanceVO();
        processInstanceVO.setProcessInstanceId(processInstance.getId())
            .setProcessInstanceName(processInstance.getName());
        return processInstanceVO;
    }

    /**
     * 获取当前任务
     *
     * @param processInstanceId 流程实例id
     * @return CurrentTaskVO
     */
    @Override
    public CurrentTaskVO currentTask(String processInstanceId) {
        List<Task> list = taskService.createTaskQuery().active().processInstanceId(processInstanceId).list();
        if (CollectionUtil.isEmpty(list)) {
            return null;
        }
        String roleId = authUtil.getUser().getRoleId();
        AtomicReference<Task> t = new AtomicReference<>();
        list.forEach(task -> {
            if (roleId.equals(task.getAssignee())) {
                t.set(task);
            }
        });
        CurrentTaskVO currentTaskVO = new CurrentTaskVO();
        currentTaskVO.setTask(t.get());
        return currentTaskVO;
    }

    /**
     * 分配任务
     *
     * @param taskId 任务id
     * @param roleId 用户id
     */
    @Override
    public void assignTask(String taskId, String roleId) {
        taskService.setAssignee(taskId, roleId);
        taskService.claim(taskId, roleId);
    }

    /**
     * 完成任务
     *
     * @param taskId 任务id
     * @param map    流程变量
     */
    @Override
    public void completeTask(String taskId, Map<String, Object> map) {
        taskService.complete(taskId, map);
    }

    /**
     * 设置下一个任务
     *
     * @param taskId 任务id
     * @return b
     */
    @Override
    public boolean nextTask(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            log.info("没有下一个任务");
            return false;
        }
        String executionId = task.getExecutionId();
        Execution execution = runtimeService.createExecutionQuery()
            .executionId(executionId)
            .singleResult();
        String activityId = execution.getActivityId();
        String deploymentId = repositoryService.createProcessDefinitionQuery()
            .processDefinitionId(task.getProcessDefinitionId())
            .singleResult()
            .getDeploymentId();
        if (activityId != null) {
            TaskAssignee taskAssignee = iTaskAssigneeService.queryByModelIdAndActivityId(deploymentId, activityId);
            if (taskAssignee == null) {
                return false;
            }
            String roleId = taskAssignee.getRoleId();
            if (roleId != null) {
                assignTask(taskId, roleId);
            }
        } else {
            log.error("没有任务节点!");
            return false;
        }
        return true;
    }

    /**
     * 结束流程
     *
     * @param processInstanceId 流程实例id
     * @param remark            备注
     */
    @Override
    public void finishProcessInstance(String processInstanceId, String remark) {
        runtimeService.deleteProcessInstance(processInstanceId, remark);
    }

    /**
     * 完成任务
     *
     * @param processInstanceId 流程id
     * @param dataMap           流程变量
     * @param state             审核状态 1-通过  0-不通过
     * @param remark            审核说明
     * @param taskId            任务id
     */
    @Override
    public void completeTaskWithPi(String processInstanceId, Map<String, Object> dataMap, Integer state, String remark, String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String taskDefinitionKey = task.getTaskDefinitionKey();
        String taskName = task.getName();
        boolean flag = true;
        if (dataMap != null) {
            Set<Map.Entry<String, Object>> entries = dataMap.entrySet();
            for (Map.Entry<String, Object> entry : entries) {
                String key = entry.getKey();
                //判断当前任务节点是否不通过结束
                if (key.equals(taskDefinitionKey)) {
                    String value = (String) entry.getValue();
                    if ("noPass".equals(value)) {
                        //结束流程
                        finishProcessInstance(processInstanceId, remark);
                        flag = false;
                    }
                }
            }
        }
        if (flag) {
            //完成任务
            completeTask(taskId, dataMap);
        }
        //新增一条审核记录
        addAdditionalData(processInstanceId, taskId, state, remark, taskName);
    }

    /**
     * 添加任务额外数据（说明，结果）
     *
     * @param processInstanceId 流程实例id
     * @param taskId            任务id
     * @param result            结果
     * @param remark            说明
     * @param taskName          节点名称
     */
    @Override
    public void addAdditionalData(String processInstanceId, String taskId, Integer result, String remark, String taskName) {
        HiAdditional hiAdditional = new HiAdditional();
        hiAdditional.setTaskId(taskId);
        hiAdditional.setRemark(remark);
        hiAdditional.setResult(result);
        hiAdditional.setTaskName(taskName);
        hiAdditional.setProInstId(processInstanceId);
        hiAdditional.setOperator(authUtil.getUser().getId());
        hiAdditional.setCreateTime(DateUtil.date());
        hiAdditional.insert();
    }

    /**
     * 启动流程 --第一个任务属于自己
     *
     * @param roleId  用户id
     * @param modelId 模型id
     * @param dataMap 流程变量
     * @param remark  说明
     * @return 流程实例id
     */
    @Override
    public ProcessInstanceVO firstTaskBelongToMe(String roleId, String modelId, Map<String, Object> dataMap, String remark) {
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        //启动流程
        if (dataMap == null) {
            dataMap = new HashMap<>();
        }
        if (model.getDeploymentId() == null) {
            return null;
        }
        dataMap.put("applyPerson", roleId);
        ProcessInstanceVO processInstanceVO = startProcess(model.getDeploymentId(), null);
        //获取流程实例
        String processInstanceId = processInstanceVO.getProcessInstanceId();
        //获取当前任务
        Task task = taskService.createTaskQuery().active().processInstanceId(processInstanceId).singleResult();
        String taskName = task.getName();
        String taskId = task.getId();
        //当前任务分配给自己
        assignTask(taskId, roleId);
        //完成任务
        completeTask(taskId, null);
        HiAdditional hiAdditional = new HiAdditional();
        hiAdditional.setTaskId(taskId);
        hiAdditional.setResult(1);
        hiAdditional.setIsFirst(1);
        hiAdditional.setTaskName(taskName);
        hiAdditional.setProInstId(processInstanceId);
        hiAdditional.setRemark("提交审核");
        hiAdditional.setOperator(roleId);
        hiAdditional.setCreateTime(new Date());
        if (remark != null) {
            hiAdditional.setRemark(remark);
        }
        hiAdditional.insert();
        return new ProcessInstanceVO().setProcessInstanceId(processInstanceId);
    }


}
